/** sps_service.cpp - sign proxy service managerment
 */

#include "sps_config.h"

#define MAX_BUFFER_LEN  (1024 * 2)

SERVICE_STATUS           SpsService::status_ = {0};
SERVICE_STATUS_HANDLE    SpsService::handle_ = NULL;
struct event_base*       SpsService::evbase_ = NULL;
std::string              SpsService::ip_;
std::string              SpsService::mac_;



/** @brief This structure used to pass parameters between event
 *   callback functions in evevnt loop.
 */
typedef struct _ev_args
{
  struct event       *ctimer;
  struct event       *netevt;
  unsigned int        retry;  /* The count of fd timeout */
  evutil_socket_t     fd;
  struct event_base  *evloop;
} ev_args;

static int load_priv_key(void *data, int argc, char **argv, char **col)
{
  int     i;
  int     init;
  time_t  end;

  /* NOTE: See createDatabase function about the database 
   *       table information.
   */
  init = atoi(argv[0]);
  if (init == 0) {
    SpsLog::instance().logWarn("call initAuthCode first");
    sps_priv_key = "";
    return 0;
  }
  
  end = atoi(argv[10]);
  if (time(NULL) > end) {
    SpsLog::instance().logWarn("exceed the time limit");
    sps_priv_key = "";
    return 0;
  }

  /* TODO: Decrypt the private key. */
  
  sps_priv_key = atoi(argv[4]) ? argv[11] : argv[12];
  SpsLog::instance().logDebug("PRIVATE KEY: %s", sps_priv_key.c_str());
  
  return 0;
}

/* This code is for debug. */
static void ev_log_cb(int severity, const char *msg)
{
  SpsLog::instance().logWarn("%d: %s", severity, msg);
}


SpsService::SpsService()
{

}

SpsService::~SpsService()
{

}

int SpsService::runService()
{
  SERVICE_TABLE_ENTRY dispatch[2];

  dispatch[0].lpServiceName = SPS_SERVICE_NAME;
  dispatch[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)serviceMain;
  dispatch[1].lpServiceName = NULL;
  dispatch[1].lpServiceProc = NULL;

  if (StartServiceCtrlDispatcher(dispatch) == FALSE) {
    SpsLog::instance().logEmerg("start service control dispatcher failed (%ld)",
				GetLastError());
    return -1;
  }
  
  return 0;
}

int SpsService::installService()
{
  DWORD      ret;
  TCHAR      filepath[MAX_PATH];
  SC_HANDLE  hscm;
  SC_HANDLE  hserv;
  sqlite3   *db;
  
  if (isInstalled()) {
    SpsLog::instance().logStderr("service is already installed");
    return 0;
  }

  if (createDatabase() == -1) {
    SpsLog::instance().logStderr("create database failed");
    return -1;
  }

  /* Open the service control manager */
  hscm = OpenSCManager(NULL, NULL, /*SC_MANAGER_ALL_ACCESS*/ SC_MANAGER_CREATE_SERVICE);
  if (hscm == NULL) {
    SpsLog::instance().logStderr("open scm faileld (%ld)", GetLastError());
    return -1;
  }

  ret = GetModuleFileName(NULL, filepath, MAX_PATH);
  if (ret == 0) {
    SpsLog::instance().logStderr("get module file name failed (%ld)",
				GetLastError());
    CloseServiceHandle(hscm);
    return -1;
  }

  hserv = CreateService(hscm, SPS_SERVICE_NAME, SPS_SERVICE_NAME,
			SERVICE_ALL_ACCESS,
			SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
			SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, filepath,
			NULL, NULL, NULL, NULL, NULL);
  if (hserv == NULL) {
    SpsLog::instance().logStderr("create service failed (%ld)", GetLastError());
    CloseServiceHandle(hscm);
    return -1;
  }

  SpsLog::instance().logStderr("install service success");
 
  CloseServiceHandle(hserv);
  CloseServiceHandle(hscm);  

  return 0;
}

int SpsService::uninstallService()
{
  SC_HANDLE hscm;
  SC_HANDLE hserv;


  if (!isInstalled()) {
    SpsLog::instance().logStderr("server has not installed before");
    return 0;
  }

  hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
  if (hscm == NULL) {
    SpsLog::instance().logStderr("open scm failed (%ld)", GetLastError());
    return -1;
  }

  hserv = OpenService(hscm, SPS_SERVICE_NAME, SERVICE_STOP | DELETE);
  if (hserv == NULL) {
    SpsLog::instance().logStderr("open service failed (%ld)", GetLastError());
    CloseServiceHandle(hscm);
    return -1;
  }

  SERVICE_STATUS status;
  ControlService(hserv, SERVICE_CONTROL_STOP, &status);

  BOOL del = DeleteService(hserv);
  CloseServiceHandle(hserv);
  CloseServiceHandle(hscm);

  if (del) {
    SpsLog::instance().logStderr("uninstall service success");
  } else {
    SpsLog::instance().logStderr("uninstall service failed (%ld)",
				 GetLastError());
  }

  destoryDatabase();
  
  return del == TRUE ? 0 : -1;
}

int SpsService::startService()
{
  return switchServiceStatus(false);
}

int SpsService::stopService()
{
  return switchServiceStatus(true);
}

/**************************************************************************
 * Private functions                                                      *
 *************************************************************************/

void WINAPI SpsService::serviceMain(DWORD argc, LPTSTR *argv)
{
  DWORD state;

  SpsLog::instance().logDebug("start the service");

  getSecretKey();
  
  handle_ = RegisterServiceCtrlHandler(SPS_SERVICE_NAME, serviceCtrl);
  if (handle_ == 0) {
    SpsLog::instance().logEmerg("register sign proxy service control "
				"handler failed (%ld)", GetLastError());
    return;
  }

  /* TODO: dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN; */
  status_.dwServiceType             = SERVICE_WIN32_OWN_PROCESS;
  status_.dwControlsAccepted        = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
  status_.dwCurrentState            = SERVICE_START_PENDING;
  status_.dwWin32ExitCode           = 0;
  status_.dwServiceSpecificExitCode = 0;
  status_.dwCheckPoint              = 1;
  status_.dwWaitHint                = 3000;

  state = SetServiceStatus(handle_, &status_);
  if (state == FALSE) {
    SpsLog::instance().logEmerg("set sign proxy service status failed",
				GetLastError());
    return;
  }

  status_.dwWin32ExitCode           = ERROR_SERVICE_SPECIFIC_ERROR;
  status_.dwServiceSpecificExitCode = -1;

  /* NOTE: Just for test */
  event_set_log_callback(ev_log_cb);
  /* End of test */
  
  /* Initialize event loop */
  ev_args args;
  args.ctimer = NULL;
  args.netevt = NULL;
  args.retry  = 0;
  args.fd     = -1;

  args.evloop = event_base_new();
  if (args.evloop == NULL) {
    SpsLog::instance().logEmerg("new event base object failed");
    goto ERR_EXIT;
  }
  /* Keep the stub, so we can stop it at ServiceCtrl function */
  evbase_ = args.evloop;

  args.ctimer = event_new(args.evloop, -1, 0, connectServer, (void *)&args);
  if (args.ctimer == NULL) {
    SpsLog::instance().logEmerg("new event object failed");
    goto ERR_EXIT;
  }

  /* Notify the SCM that service is running */
  status_.dwCurrentState  = SERVICE_RUNNING;
  status_.dwCheckPoint    = 0;
  status_.dwWin32ExitCode = 0;
  status_.dwWaitHint      = 0;
  state = SetServiceStatus(handle_, &status_);
  if (state == FALSE) {
    SpsLog::instance().logEmerg("notify the scm failed and exit (%ld)",
				GetLastError());
    goto ERR_EXIT;
  }

  struct timeval tv;
  evutil_timerclear(&tv);
  tv.tv_sec  = SpsConf::instance().get_reconnect_time_sec();
  tv.tv_usec = SpsConf::instance().get_reconnect_time_usec();
  int ret = event_add(args.ctimer, &tv);
  if (ret == -1) {
    SpsLog::instance().logEmerg("add connection event failed");
    goto ERR_EXIT;
  }

  ret = event_base_loop(args.evloop, 0x04);
  if (ret == -1) {
    SpsLog::instance().logEmerg("enter the event loop failed");
    goto ERR_EXIT;
  }

  /* Terminate the service */
  status_.dwWin32ExitCode           = 0;
  status_.dwServiceSpecificExitCode = 0;
 

 ERR_EXIT:

  status_.dwCurrentState            = SERVICE_STOPPED;
  SetServiceStatus(handle_, &status_);

  if (args.ctimer) {
    event_free(args.ctimer);
  }
  
  if (args.evloop) {
    event_base_free(args.evloop);
    evbase_ = NULL;
  }
  
  if (args.fd != -1) {
    evutil_closesocket(args.fd);
  }
  
  SpsLog::instance().logDebug("stop the service");
}

void WINAPI SpsService::serviceCtrl(DWORD ctrl)
{
  switch (ctrl) {

  case SERVICE_CONTROL_STOP:
  case SERVICE_CONTROL_SHUTDOWN: {
    status_.dwCurrentState = SERVICE_STOP_PENDING;
    status_.dwWaitHint = 100;
    status_.dwCheckPoint = 1;

    SetServiceStatus(handle_, &status_);

    /* Stop the event loop */
    if (evbase_) {
      int ret = event_base_loopbreak(evbase_);
      if (ret == -1) {
	SpsLog::instance().logAlert("stop the event loop failed");
      }
    }
    
    break;
  }

  case SERVICE_CONTROL_INTERROGATE:
    SetServiceStatus(handle_, &status_);
    
    break;
    
  default:
    break;
  }
}

evutil_socket_t SpsService::tcpConnect(const char *host, const char *serv)
{
  int                    ret;
  evutil_socket_t        fd = -1;
  struct evutil_addrinfo hints, *res, *p;

  memset(&hints, 0, sizeof(hints));
  hints.ai_family   = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;

  ret = evutil_getaddrinfo(host, serv, &hints, &res);
  if (ret) {
    SpsLog::instance().logEmerg("get %s:%s address faild (%d:%s)",
				host, serv, ret, evutil_gai_strerror(ret));
    return -1;
  }

  p = res;

  do {
    fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if (fd == INVALID_SOCKET) {
      continue;
    }

    if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) {
      ip_ = getIpAddress(fd);
      mac_ = getMacAddress(ip_);
      break;
    }

    evutil_closesocket(fd);
    fd = -1;
  } while ((res = res->ai_next) != NULL);

  if (res == NULL) {
    SpsLog::instance().logEmerg("connect to %s:%s failed", host, serv);
  }

  evutil_freeaddrinfo(p);
  
  return fd;
}

void SpsService::connectServer(evutil_socket_t fd, short what, void *data)
{
  ev_args         *args = (ev_args *)data;
  struct timeval  tv;
  evutil_socket_t sock;
  const char      *host, *serv;

  host = SpsConf::instance().get_host();
  serv = SpsConf::instance().get_serv();
  sock = tcpConnect(host, serv);
  if (sock == -1) {  /* Connect failed and reconnect. */
    evutil_timerclear(&tv);
    tv.tv_sec  = SpsConf::instance().get_reconnect_time_sec();
    tv.tv_usec = SpsConf::instance().get_reconnect_time_usec();

    event_add(args->ctimer, &tv);

    return;
  }

  /* Connect success and delete the connection timer. */
  event_del(args->ctimer);

  evutil_make_socket_nonblocking(sock);

  /* NOTE: If this is a reconnection, you can use event_assign
   *       function to avoid allocating memory from heap at
   *       each time.
   */
  args->netevt = event_new(args->evloop, sock,
			   EV_READ | EV_TIMEOUT | EV_PERSIST,
			   netReadEvent, data);
  if (args->netevt == NULL) {
    /* NOTE: Should close the connection? */
    SpsLog::instance().logEmerg("new network reading event object failed");
    return;
  }

  args->fd = sock;

  evutil_timerclear(&tv);
  tv.tv_sec  = SpsConf::instance().get_connect_timeout_sec();
  tv.tv_usec = SpsConf::instance().get_connect_timeout_usec();
  event_add(args->netevt, &tv);
}

void SpsService::netReadEvent(evutil_socket_t fd, short what, void *data)
{
  char       buff[MAX_BUFFER_LEN] = {0};
  ev_args   *args = (ev_args *)data;

  if (what & EV_TIMEOUT) {
    SpsLog::instance().logDebug("connection timeout %d", args->retry);
    args->retry++;
    if (args->retry >= SpsConf::instance().get_timeout_wait_num()) {
      SpsLog::instance().logAlert("lose connection and reconnect");

      reconnectServer(args);
      return;
    }
  }

  if (what & EV_READ) {
    size_t len = recv(fd, buff, MAX_BUFFER_LEN, 0);
    if (len == 0) {
      SpsLog::instance().logAlert("connection was closed by peer");
      reconnectServer(args);
      return;
    } else if (len == SOCKET_ERROR) {
      DWORD status = WSAGetLastError();
      if (WSAEWOULDBLOCK == status) {
	return;
      }

      args->retry++;
      if (args->retry >= SpsConf::instance().get_timeout_wait_num() ||
	  WSAECONNRESET == status || WSAECONNABORTED == status)	{
	reconnectServer(args);
	return;
      }

      /* Receive data failed */
      return;
    }

    /* Receive data from server */
    buff[len] = 0;
    args->retry = 0;
    
    SpsReqFactroy  f;
    SpsReq        *req;
    std::string    reply;

    /* NOTE: Printing received inforamtion */
    SpsLog::instance().logDebug(buff);
    /* NOTE: End printing */
    
    req = f.loadRequest(buff, ip_, mac_);
    req->handleRequest();
    reply = req->getResponse();

    /* NOTE: Printing sending information */
    SpsLog::instance().logDebug(reply.c_str());
    /* NOTE: End printing */
    
    len = send(fd, reply.c_str(), reply.length(), 0);
    if (len == SOCKET_ERROR) {
      DWORD status = WSAGetLastError();
      if (WSAEWOULDBLOCK == status) {
	return;
      }

      args->retry++;
      if (args->retry >= SpsConf::instance().get_timeout_wait_num() ||
	  WSAECONNRESET == status || WSAECONNABORTED == status)	{
	reconnectServer(args);
	return;
      }

      return;
    }
    
  }  /* End (what & EV_READ) */
}

int SpsService::disconnectServer(struct _ev_args *args)
{
  event_del(args->netevt);
  event_free(args->netevt);
  args->netevt = NULL;

  evutil_closesocket(args->fd);
  args->fd = -1;
  args->retry = 0;

  return 0;
}

int SpsService::reconnectServer(struct _ev_args *args)
{
  struct timeval  tv;
  
  disconnectServer(args);
  
  evutil_timerclear(&tv);
  tv.tv_sec = 1;
  event_add(args->ctimer, &tv);
  
  return 0;
}

int SpsService::createDatabase()
{
  int       rc;
  char     *sql;
  char     *errmsg;
  sqlite3  *db;

  rc = sqlite3_open(sps_db_name.c_str(), &db);
  if (rc != SQLITE_OK) {
    SpsLog::instance().logEmerg("database error: %s", sqlite3_errmsg(db));
    goto END;
  }

  sql = "CREATE TABLE AUTH_INFO(" \
    "init                INT," \
    "id                  CHAR(100)," \
    "apiKey              CHAR(100)," \
    "useVersion          CHAR(10)," \
    "useMode             INT," \
    "useSituation        INT," \
    "usePermission       INT," \
    "userId              INT," \
    "state               INT," \
    "startDate           INTEGER," \
    "endDate             INTEGER," \
    "secretKey           CHAR(100)," \
    "testSecretKey       CHAR(100));" \
    "INSERT INTO AUTH_INFO VALUES " \
    "(0, NULL, NULL, NULL, -1, -1, -1, -1, -1, -1, -1, NULL, NULL);";

  rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
  if (rc != SQLITE_OK) {
    SpsLog::instance().logEmerg("database error: %s", errmsg);
    sqlite3_free(errmsg);
    goto END;
  }
  
 END:
  
  sqlite3_close(db);
  
  return rc == SQLITE_OK ? 0 : -1;
}

int SpsService::destoryDatabase()
{
  if (remove(sps_db_name.c_str()) == 0) {
    return 0;
  }

  SpsLog::instance().logEmerg("remove database file failed"); 
  return -1;
}

void SpsService::getSecretKey()
{
  int       rc;
  char     *sql;
  char     *msg;
  sqlite3  *db;

  rc = sqlite3_open(sps_db_name.c_str(), &db);
  if (rc != SQLITE_OK) {
    SpsLog::instance().logWarn("load key: open failed(%s)", sqlite3_errmsg(db));
    goto ERR_EXIT;
  }

  sql = "SELECT * FROM AUTH_INFO;";

  rc = sqlite3_exec(db, sql, load_priv_key, NULL, &msg);
  if (rc != SQLITE_OK) {
    SpsLog::instance().logWarn("load key: exec select failed(%s)", msg);
    sqlite3_free(msg);
  }

 ERR_EXIT:

  sqlite3_close(db);
}


/*******************************************************************************
 * Private non-static functions                                                *
 ******************************************************************************/

bool SpsService::isInstalled()
{
  bool      ret = false;
  SC_HANDLE hscm = NULL;
  SC_HANDLE hserv = NULL;

  /* Open the service control manager */
  hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE);
  if (hscm != NULL) {
    /* Open the service */
    hserv = OpenService(hscm, SPS_SERVICE_NAME, SERVICE_QUERY_CONFIG);
    if (hserv) {
      ret = true;
      CloseServiceHandle(hserv);
    }
    CloseServiceHandle(hscm);
  }

  return ret;
}

int SpsService::switchServiceStatus(bool stop)
{
  int             ret = -1;
  SC_HANDLE       hscm = NULL;
  SC_HANDLE       hserv = NULL;
  SERVICE_STATUS  st;

  if (!isInstalled()) {
    SpsLog::instance().logStderr("service is not installed");
    goto ERR_EXIT;
  }

  hscm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
  if (hscm == NULL) {
    SpsLog::instance().logStderr("open scm failed (%ld)", GetLastError());
    goto ERR_EXIT;
  }

  hserv = OpenService(hscm, SPS_SERVICE_NAME, SERVICE_ALL_ACCESS);
  if (hserv == NULL) {
    SpsLog::instance().logStderr("open service failed (%ld)", GetLastError());
    goto ERR_EXIT;
  }

  BOOL ok = QueryServiceStatus(hserv, &st);
  if (ok == FALSE) {
    SpsLog::instance().logStderr("query service status failed (%ld)",
				 GetLastError());
    goto ERR_EXIT;
  }

  if (stop) {
    /* stop the service */
    if (st.dwCurrentState != SERVICE_STOPPED) {
      ok = ControlService(hserv, SERVICE_CONTROL_STOP, &st);
      if (ok == FALSE) {
	SpsLog::instance().logStderr("stop service failed (%ld)",
				     GetLastError());
      } else {
	SpsLog::instance().logStderr("stop service success");
	ret = 0;
      }
      
    } else {
      SpsLog::instance().logStderr("service has already stopped");
    }
    
  } else {
    if (st.dwCurrentState == SERVICE_STOPPED) {
      ok = StartService(hserv, 0, NULL);
      if (ok == FALSE) {
	SpsLog::instance().logStderr("start service failed (%ld)",
				     GetLastError());
      } else {
	SpsLog::instance().logStderr("start service success");
	ret = 0;
      }
      
    } else {
      SpsLog::instance().logStderr("service is already running");
    }
    
  }
  
 ERR_EXIT:
  
  if (hserv) {
    CloseServiceHandle(hserv);
  }
  
  if (hscm) {
    CloseServiceHandle(hscm);
  }

  return ret;
}

std::string SpsService::getIpAddress(evutil_socket_t s)
{
  struct sockaddr_storage ss;
  struct sockaddr *sa;
  int len = sizeof(ss);
  int ret;

  ret = getsockname(s, (struct sockaddr *)&ss, &len);
  if (ret == SOCKET_ERROR) {
    SpsLog::instance().logError("get ip address failed");
    return "";
  }

  sa = (struct sockaddr *)&ss;
  char buff[100] = {0};

  if (sa->sa_family == AF_INET) {
    struct sockaddr_in *sin = (struct sockaddr_in *)sa;
    evutil_inet_ntop(sin->sin_family, (void *)&sin->sin_addr, buff, 100);
  } else if (sa->sa_family == AF_INET6) {
    struct sockaddr_in6 *sin = (struct sockaddr_in6 *)sa;
    evutil_inet_ntop(sin->sin6_family, (void *)&sin->sin6_addr, buff, 100);
  }

  return std::string(buff);
}

std::string SpsService::getMacAddress(const std::string& ip)
{
  PIP_ADAPTER_INFO info = NULL, p;
  ULONG len, ret;

  GetAdaptersInfo(info, &len);
  info = (PIP_ADAPTER_INFO)malloc(len);
  if (info == NULL) {
    SpsLog::instance().logError("malloc memory failed while get mac");
    return "";
  }

  ret = GetAdaptersInfo(info, &len);
  if (ret != ERROR_SUCCESS) {
    SpsLog::instance().logError("get adapter info failed");
    free(info);
    return "";
  }

  p = info;
  std::string mac;
  while (p) {
    if (p->IpAddressList.IpAddress.String == ip) {
      char buff[4];
      for (UINT i = 0; i < p->AddressLength; ++i) {
	snprintf(buff, 4, "%02X-", p->Address[i]);
	mac += buff;
      }
      mac = mac.substr(0, mac.length() - 1);
      break;
    }
    p = p->Next;
  }

  if (p == NULL) {
    SpsLog::instance().logError("can not find %s mac address", ip.c_str());
  }

  free(info);

  return mac;
}
